package rps.client.ai;

import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Vector;
import java.lang.Math;

import rps.client.ArrayShuffle;
import rps.client.GameListener;
import rps.game.Game;
import rps.game.data.Move;
import rps.game.data.Figure;
import rps.game.data.FigureKind;
import rps.game.data.Player;
import rps.game.data.AttackResult;

/**
 * This class contains an advanced AI, that should participate in the
 * tournament.
 */
public class TournamentAi implements GameListener {

	private Player player = new Player("Tournament AI (#49)");
	private Game game;
	private FigureKind [] preAssignment = new FigureKind[42];
	private int rows = 6, cols = 7;
	int moveFrom, moveTo;
	private final int maxDurationForMoveInMilliSeconds;
	private final int maxDurationForAllMovesInMilliSeconds;
	private HashMap<Integer,Figure> movedFigures = new HashMap<Integer, Figure>();

	public TournamentAi(int maxDurationForMoveInMilliSeconds, int maxDurationForAllMovesInMilliSeconds) {
		this.maxDurationForMoveInMilliSeconds = maxDurationForMoveInMilliSeconds;
		this.maxDurationForAllMovesInMilliSeconds = maxDurationForAllMovesInMilliSeconds;
	}

	/**
	 * returns a random integer
	 * 
	 * @param low
	 * @param high
	 * @return
	 */
	private int myRandom(int low, int high) {
		high++;
		return (int) (Math.random() * (high - low) + low);
	}

	/**
	 * makes a random choice
	 * 
	 * @return returns a FigureKind (ROCK, PAPER or SCISSORS)
	 */
	private FigureKind makeChoice() {
		int x = myRandom(1, 3);
		switch (x) {
		case 1: {
			return FigureKind.PAPER;
		}
		case 2: {
			return FigureKind.ROCK;
		}
		case 3: {
			return FigureKind.SCISSORS;
		}
		default:
			return FigureKind.ROCK;
		}
	}

	/**
	 * generates a random Assignment
	 * 
	 * @param defaultAssignment
	 */
	private void generateRandomAssignment(FigureKind[] defaultAssignment) {

		ArrayShuffle.shuffle(defaultAssignment);

		for (int i = 0; i < (42 - 14); i++) {
			preAssignment[i] = null;
		}
		for (int i = 0; i < 14; i++) {
			preAssignment[28 + i] = defaultAssignment[i];
		}
	}

	// ===========================================================================================

	// ========================Bestimmen der moeglichen Zuege=================================
	/**
	 * checks if the given Figure can be moved
	 * 
	 * @param f
	 *            Figure
	 * @return true if f belongs to player and is not a FLAG or a TRAP
	 */
	private boolean validateFigure(Figure f, Player player) {
		boolean result = ((f.getKind() != FigureKind.FLAG) && (f.getKind() != FigureKind.TRAP));
		return ((f.belongsTo(player)) && result);
	}

	/**
	 * checks if row and col are inside the boards borders
	 * 
	 * @param row
	 * @param col
	 * @return true if row and col are inside the field, else false
	 */
	private boolean validateField(int row, int col) {
		return ((row >= 0) && (row < rows) && (col >= 0) && (col < cols));
	}

	/**
	 * checks if the the given row and col points to a valid destination
	 * 
	 * @param field
	 *            board
	 * @param row
	 * @param col
	 * @return true if ai can move to this field, else false
	 */
	private boolean checkField(Figure[] field, int row, int col, Player player) {
		return ((field[toIndex(row, col)] == null) || !(field[toIndex(row, col)]
				.belongsTo(player)));
	}
/**
 * converts the row and col into an arrayindex
 * @param row
 * @param col
 * @return
 */
	private int toIndex(int row, int col) {
		return (col + row * cols);
	}
    /**
     * checks if the given field is a valid destination
     * @param field
     * @param row
     * @param col
     * @param player
     * @return
     */
	private boolean validateDestination(Figure[] field, int row, int col, Player player) {
		if (validateField(row, col) && (checkField(field, row, col, player)))
			return true;
		return false;
	}

	/**
	 * checks the adjacent fields around the current field and returns those to
	 * which the ai can move
	 * 
	 * @param field
	 *            board
	 * @param row
	 * @param col
	 * @return returns a Vector of integer arrays
	 */
	private Vector<Move> possibleMoves(Figure[] field, int row, int col, Player player) {
		Vector<Move> moves = new Vector<Move>();
		int x = col, y = row, fig = toIndex(y, x);
		// check field above the current field
		if (validateDestination(field, --row, col, player))
			moves.add(new Move(fig, toIndex(row, col), field));
		row = y;
		// check field under the current field
		if (validateDestination(field, ++row, col, player))
			moves.add(new Move(fig, toIndex(row, col), field));
		row = y;
		// check field to the left of the current field
		if (validateDestination(field, row, --col, player))
			moves.add(new Move(fig, toIndex(row, col), field));
		col = x;
		// check field to the right of the current field
		if (validateDestination(field, row, ++col, player))
			moves.add(new Move(fig, toIndex(row, col), field));
		col = x;
		return moves;
	}
    /**
     * returns all possible moves for a player
     * @param field
     * @param player
     * @return
     */
	private Vector<Move> playersNextMoves(Figure[] field, Player player) {
		Vector<Move> result = new Vector<Move>();
		for (int j = 0; j < rows; ++j)
			for (int i = 0; i < cols; ++i)
				if (field[toIndex(j, i)] != null)
					if (validateFigure(field[toIndex(j, i)], player)) {
						Vector<Move> newmoves = possibleMoves(field, j, i, player);
						result.addAll(newmoves);
					}
		return result;
	}

	// =============================================================================================

	// ===================================Heuristik=================================================
	/**
	 * rates a field
	 * @param field
	 * @param index
	 * @return
	 */
	private int rateField(Figure[] field, int index) {
		if (index < 0 || index > 41)
			return 0;
		if (field[index] == null)
			return 0;
		if (field[index].belongsTo(player))
			return 1;
		else
			return -1;
	}
	/**
	 * rates the surroundings of a field
	 * @param field
	 * @param row
	 * @param col
	 * @return
	 */
	private int rateMove(Figure[] field, int row, int col) {
		int rating = 0;
		// check field above the current field
		rating = rating + rateField(field, toIndex(--row, col));
		row += 2;
		// check field under the current field
		rating = rating + rateField(field, toIndex(row, col));
		--row;
		// check field to the left of the current field
		rating = rating + rateField(field, toIndex(row, --col));
		col += 2;
		// check field to the right of the current field
		rating = rating + rateField(field, toIndex(row, col));
		return rating;
	}
	/**
	 * rate the board
	 * @param field
	 * @return
	 */
	private int rate(Figure[] field, Move move) {
		int myfigures = 0;
		int enemyfigures = 0;
		int rating;
		for (int i = 0; i < field.length; ++i) {
			if (field[i] != null)
				if (field[i].belongsTo(player))
					myfigures++;
				else
					enemyfigures++;
		}
		int row = move.getTo() / cols;
		int col = move.getTo() % cols;
		rating = myfigures - enemyfigures;
		rating = ++rating + rateMove(field, row, col);
		return rating;
	}

	// ========================================================================================

	// ============================Bewegungs- und Kampfsimulation=========================
    /**
     * simulates a step from an index to an index
     * @param field
     * @param move
     * @return
     */
	private Figure[] step(Figure[] field, Move move) {
		Figure[] result = field.clone();
		result[move.getTo()] = result[move.getFrom()];
		result[move.getFrom()] = null;
		return result;
	}
    /**
     * simulates a lost fight
     * @param field
     * @param move
     * @return
     */
	private Figure[] steplost(Figure[] field, Move move) {
		Figure[] result = field.clone();
		result[move.getFrom()] = null;
		return result;
	}
    /**
     * simulate a move
     * @param field
     * @param move
     * @return
     */
	private Figure[] simulateMove(Figure[] field, Move move) {
		if (field[move.getTo()] == null) // Zug ohne Kampf
			return step(field, move);
		if (field[move.getTo()].isDiscovered()) { // Kampf mit vohersagbarem Ergebnis
			int result = fight(field, move);
			if (result == 2)
				return step(field, move);
			else if (result == -1)
				return steplost(field, move);
			else return step(field, move);
		} else {// Kampf auf Basis von Erwartungswert
			return step(field, move);
		}
	}
    /**
     * simulate a fight
     * @param field
     * @param move
     * @return
     */
	private int fight(Figure[] field, Move move) {
		if (field[move.getFrom()].attack(field[move.getTo()]) == AttackResult.WIN)
			return 2;
		if (field[move.getFrom()].attack(field[move.getTo()]) == AttackResult.LOOSE)
			return -1;
		if (field[move.getFrom()].attack(field[move.getTo()]) == AttackResult.DRAW)
			return 1;
		return 0;
	}

	// ======================================================================================
	/**
	 * checks a move
	 * @param field
	 * @param move
	 * @return
	 */
	private int chMv(Figure[] field, Move move){
		if (field[move.getTo()] == null)
			return 0;
		if (field[move.getTo()].isDiscovered()) {
			return fight(field, move);
		} else
			return 1;
	}
    /**
     * tries to find a good move
     * @param field
     * @param moves
     * @param start
     * @return
     */
	private Move findgoodmove(Figure[] field, Vector<Move> moves, long start) {
		int highest = 0;
		int rating = -10;
		int rat;
		for (int i = 0; i < moves.size(); ++i) {
			if ((System.nanoTime()-start) / 1000000 >=  (maxDurationForMoveInMilliSeconds)-2) break;
			rat = chMv(field, moves.get(i));
			if (rat > rating) {
				highest = i;
				rating = rat;
			}
		}
		System.out.println(rating);
		return moves.get(highest);
	}

	@Override
	public void chatMessage(Player sender, String message)
			throws RemoteException {
		// TODO Auto-generated method stub

	}

	@Override
	public void provideInitialAssignment(Game game) throws RemoteException {
		this.game = game;
		// TODO Auto-generated method stub
		FigureKind[] preAssignment = new FigureKind[] { FigureKind.ROCK,
				FigureKind.ROCK, FigureKind.ROCK, FigureKind.ROCK,
				FigureKind.PAPER, FigureKind.PAPER, FigureKind.PAPER,
				FigureKind.PAPER, FigureKind.SCISSORS, FigureKind.SCISSORS,
				FigureKind.SCISSORS, FigureKind.SCISSORS, FigureKind.TRAP,
				FigureKind.FLAG };
		generateRandomAssignment(preAssignment);
		try {
			game.setInitialAssignment(player, this.preAssignment);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void provideInitialChoice() throws RemoteException {
		// TODO Auto-generated method stub
		FigureKind choice = makeChoice();
		game.setInitialChoice(player, choice);
	}

	@Override
	public void startGame() throws RemoteException {
		// TODO Auto-generated method stub

	}

	@Override
	public void provideNextMove() throws RemoteException {
		long moveCalculationStartedAt = System.nanoTime();
		// TODO Auto-generated method stub
		Move move=findgoodmove(game.getField(), playersNextMoves(game.getField(), player), moveCalculationStartedAt);
		game.move(player, move.getFrom(), move.getTo());
	}

	@Override
	public void figureMoved() throws RemoteException {
		int index =game.getLastMove().getFrom();
        if (movedFigures.containsKey(index)) movedFigures.remove(index);
	}

	@Override
	public void figureAttacked() throws RemoteException {
		int index = game.getLastMove().getFrom();
        if (movedFigures.containsKey(index)) movedFigures.remove(index);

	}

	@Override
	public void provideChoiceAfterFightIsDrawn() throws RemoteException {
		game.setUpdatedKindAfterDraw(player, makeChoice());

	}

	@Override
	public void gameIsLost() throws RemoteException {
		// TODO Auto-generated method stub

	}

	@Override
	public void gameIsWon() throws RemoteException {
		// TODO Auto-generated method stub

	}

	@Override
	public void gameIsDrawn() throws RemoteException {
		// TODO Auto-generated method stub

	}

	@Override
	public String toString() {
		return player.getNick();
	}

	@Override
	public Player getPlayer() throws RemoteException {
		// TODO Auto-generated method stub
		return player;
	}
}